package com.fasteat.search.template;

import com.fasteat.search.entity.Page;
import com.fasteat.search.entity.SearchCondition;
import com.fasteat.common.util.CamelUtil;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author ghost
 * @Description:抽象搜索类
 * @since 2021-1-25
 */
public abstract class AbstractSearch implements Search, Serializable {

	@Qualifier("restHighLevelClient")
	@Autowired
	private RestHighLevelClient client; // 请求客户端

	private String index; // 索引

	private SearchSourceBuilder sourceBuilder; // 建立文档查询表达式

	private SearchCondition condition; // 搜索条件

	private SearchResponse response; // 响应结果

	private HighlightBuilder highlightBuilder; // 高亮规则

	private Map<String, SortOrder> sortMap; // 排序规则


	public void before() {

	}

	public void after() {

	}

	/**
	 * 初始化index
	 */
	public abstract void initIndex();


	public void setIndex(String index) {
		this.index = index;
	}

	/**
	 * 模版方法
	 *
	 * @param searchCondition
	 * @return
	 */
	@Override
	public Page search(SearchCondition searchCondition) {
		before();
		if (StringUtils.isEmpty(index)) {
			initIndex();
		}
		this.condition = searchCondition;
		// 构建搜索表达式
		sourceBuilder = buildSourceSearch(condition.getEntry(), condition.getCurrent(), condition.getSize());
		// 高亮
		if (highlightBuilder != null) {
			sourceBuilder.highlighter(highlightBuilder);
		}
		// 排序
		if (sortMap != null && sortMap.size() > 0) {
			openSort();
		}
		// 发送请求获取响应结果
		response = sendRequest(sourceBuilder);
		// 封装结果集
		Page page = encapsulateResult(response, searchCondition.getCurrent(), searchCondition.getSize());
		after();
		return page;
	}

	/**
	 * 通过ID删除文档
	 *
	 * @param id
	 * @return
	 */
	public boolean delete(String id) {
		if (StringUtils.isEmpty(index)) {
			initIndex();
		}
		int statusCode = 0;
		try {
			DeleteRequest request = new DeleteRequest(index, id);
			statusCode = client.delete(request, RequestOptions.DEFAULT).status().getStatus();
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		return statusCode == 200;
	}

	/**
	 * 构建搜索表达式子
	 *
	 * @return
	 */
	private SearchSourceBuilder buildSourceSearch(Map<String, String> entry, int current, int size) {
		// 构建文档查询表达式
		SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
		// 构建查询条件
		BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
		entry.keySet().forEach(key ->
				queryBuilder.should(QueryBuilders.matchQuery(key, entry.get(key))));
		// 设置查询的页码、尺寸
		current = current <= 0 ? 0 : current - 1;
		size = size <= 0 ? 5 : size;
		searchSourceBuilder.from(current * size).size(size)
				.timeout(TimeValue.timeValueSeconds(2));
		// 将查询条件放入文档查询中
		searchSourceBuilder.query(queryBuilder);
		return searchSourceBuilder;
	}

	/**
	 * 设置排序
	 *
	 * @return
	 */
	public AbstractSearch sort(Map<String, SortOrder> map) {
		this.sortMap = map;
		return this;
	}

	public AbstractSearch sort(String field, SortOrder sortOrder) {
		if (sortMap == null) {
			synchronized (this) {
				if (sortMap == null) {
					this.sortMap = new HashMap<>(4);
				}
			}
		}
		sortMap.put(field, sortOrder);
		return this;
	}

	/**
	 * 进行排序
	 */
	private void openSort() {
		sortMap.keySet().forEach(key -> {
			sourceBuilder.sort(key, (SortOrder) sortMap.get(key));
		});
	}

	/**
	 * 设置高亮
	 */
	public AbstractSearch highlight(HighlightBuilder highlightBuilder) {
		this.highlightBuilder = highlightBuilder;
		return this;
	}

	/**
	 * 发送搜索请求，并返回搜索结果
	 */
	private SearchResponse sendRequest(SearchSourceBuilder sourceBuilder) {
		// 构建查询请求
		SearchRequest request = new SearchRequest(index);
		// 将查询表达式放入查询请求中
		request.source(sourceBuilder);
		// 发送请求获取响应内容
		SearchResponse response = null;
		try {
			response = client.search(request, RequestOptions.DEFAULT);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return response;
	}

	/**
	 * 封装结果集
	 */
	private Page encapsulateResult(SearchResponse response, int current, int size) {
		Page page = new Page(current, size);
		List<Map<String, Object>> ret = new ArrayList<>(size);
		for (SearchHit hit : response.getHits().getHits()) {
			// 获取文档内容（不包含高亮）
			Map<String, Object> sourceMap = hit.getSourceAsMap();
			// 获取高亮部分
			if (highlightBuilder != null) {
				for (String keyword : condition.getEntry().keySet()) {
					// 获取高亮碎片化文本
					HighlightField highlightField = hit.getHighlightFields().get(keyword);
					if (highlightField == null) {
						continue;
					}
					Text[] text = highlightField.getFragments();
					// 将高亮后的字段文本构建成字符串
					StringBuffer stringBuffer = new StringBuffer();
					for (Text s : text) {
						stringBuffer.append(s);
					}
					// 将高亮的字段字符串替换sourceMap中没有高亮的字段
					sourceMap.put(keyword, stringBuffer.toString());
				}
			}
			// 删除一些字段
			sourceMap.remove("@timestamp");
			sourceMap.remove("@version");
			// 转化成驼峰
			sourceMap = CamelUtil.formatMapWithCamel(sourceMap);
			ret.add(sourceMap);
		}
		long total = response.getHits().getTotalHits().value;
		page.setRecords(ret);
		page.setTotal(total);
		page.setPages(total / size + (total % size == 0 ? 0 : 1));
		return page;
	}


}
